package com.lemmsoft.offthehook;

import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.VIBRATE_SETTING_ON;
import static android.media.AudioManager.VIBRATE_TYPE_NOTIFICATION;
import static android.media.AudioManager.VIBRATE_TYPE_RINGER;

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;

public class OffTheHookService extends Service implements SensorEventListener, Runnable	{
	// State constants
	public final static int UNREGISTERED 	= -1;
	public final static int MOVING 			=  0;
	public final static int FACE_DOWN 		=  1;
	public final static int FACE_UP 		=  2;
	
	// Profile constants
	//  - be sure to update the corresponding data in arrays.xml if you change any of these
	public final static int PROFILE_VIBRING		= 0;
	public final static int PROFILE_NORMAL 		= 1;
	public final static int PROFILE_NIGHT 		= 2;
	public final static int PROFILE_VIBRATE		= 3;	
	public final static int PROFILE_SILENT 		= 4;
	
	// Sensor and state information
	private SensorEventListener mListener = this;
	private SensorManager 		mSensorManager;
	private Sensor				mOrientation;
	private int 				mCurrentState = UNREGISTERED;
	
	// Scheduler
	private final ScheduledExecutorService 	scheduler = Executors.newScheduledThreadPool(1);
	private ScheduledFuture<?> 				scheduledFuture;
	
	// Settings
	private SharedPreferences	mSettings;   
	private boolean				mRunning;
	private boolean				mNotify;
	private int					mInterval;
	private float 				mSensitivity;
	
	
	// Interprocess communication interface implementation
	private final IOffTheHookService.Stub	mBinder = new IOffTheHookService.Stub()	{
		@Override
		public boolean running() throws RemoteException {
			return mRunning;
		}

		@Override
		public void setRunning(boolean startRunning) throws RemoteException {
			if(mOrientation != null)	{
				mRunning = startRunning ? startListening() : stopListening();
				
				mSettings.edit().putBoolean("Running", mRunning).commit();
			}
			else	{
				throw new RemoteException();
			}
		}
		
		@Override
		public int getProfile(int state) throws RemoteException {
			return mSettings.getInt("Profile"+state, PROFILE_NORMAL);
		}

		@Override
		public void setProfile(int state, int profile) throws RemoteException {
			mSettings.edit().putInt("Profile"+state, profile).commit();
			
			if(mCurrentState == state && mRunning)	{
				changeProfile(profile);
			}
		}

		@Override
		public boolean getNotifyOnChange() throws RemoteException {
			return mNotify; 
		}

		@Override
		public void setNotifyOnChange(boolean newValue) throws RemoteException {
			mSettings.edit().putBoolean("NotifyOnChange", newValue).commit();
			mNotify = newValue;
		}
	};
	
	/* Activity Interface -----------------------------------------------------------------------*/
	@Override
	public void onCreate()	{
		super.onCreate();
		Log.i("OTH", "Creating Off The Hook service");
		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		
	    List<Sensor> availableSensors = mSensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
	    if(availableSensors.size() == 1)	{
	    	mOrientation = availableSensors.get(0);
	    }
	    
	    mSettings = getSharedPreferences("OffTheHookSettings", 0);
	    
	    // If this is the first run, set some sane defaults
	    if(mSettings.getBoolean("FirstRun", true))	{
			SharedPreferences.Editor editor = mSettings.edit();
			
	    	editor.putInt("Profile" + FACE_UP, 		PROFILE_NORMAL	);
	    	editor.putInt("Profile" + FACE_DOWN,	PROFILE_NIGHT	);
	    	editor.putInt("Profile" + MOVING, 		PROFILE_VIBRING	);
	    	
	    	editor.putBoolean("FirstRun", false);
	    	editor.commit();
	    }
	    
	    // Retrieve extra settings
	    mNotify 		= mSettings.getBoolean	("NotifyOnChange", 	true);
		mInterval 		= 0;//mSettings.getInt		("Interval", 		10	);
		mSensitivity 	= mSettings.getFloat	("Sensitivity", 	6.0f);
	    
	    // If the service has been killed due to low memory and then restarted, start 
	    // listening immediately.
	    if(mSettings.getBoolean("Running", false))	{
	    	try {
				mBinder.setRunning(true);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
	    }
	    
	}

	@Override
	public void onDestroy()	{
		super.onDestroy();
		Log.i("OTH", "Destroying service");
		if(mRunning)	{
			stopListening();
		}
	}
	
	@Override
	public IBinder onBind(Intent intent) {	
		Log.i("OTH", "Binding to service");
		return mBinder;	
	}	
	
	@Override
	public boolean onUnbind(Intent i)	{
		Log.i("OTH", "Unbinding from service");
		if(!mRunning)	{
			stopSelf();
		}
		return super.onUnbind(i);
	}
	
	/* Runnable Interface -----------------------------------------------------------------------*/
	@Override
	public void run()	{
		// Register listener, when it gets a reading it will unregister itself
		mSensorManager.registerListener(mListener, mOrientation, SensorManager.SENSOR_DELAY_UI);
	}

	/* SensorEventListener Interface ------------------------------------------------------------*/
	@Override
	public void onSensorChanged(SensorEvent event) {
		float pitch = Math.abs(event.values[1]);
		float roll  = Math.abs(event.values[2]);
		
		//Log.i("OTH", "Pitch: " + pitch + ", Roll: " + roll);
		
		if(roll <= mSensitivity)	{
			// The phone is still and on its front/back
			if(about(pitch, 0.0f))	{
				registerState(FACE_UP);
			}
			else	{
				if(about(pitch, 180.0f))	{
					registerState(FACE_DOWN);				
				}
			}
		}
		else	{
			registerState(MOVING);
		}
		
		// If sample rate is continuous, don't bother unregistering the SensorEventListener
		if(mInterval != 0)	{
			mSensorManager.unregisterListener(mListener);
		}
	}
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {}	// Irrelevant to us (mostly)
	
	/* Private stuff ----------------------------------------------------------------------------*/
	private boolean startListening()	{
		if(mInterval == 0)	{
			mSensorManager.registerListener(mListener, mOrientation, SensorManager.SENSOR_DELAY_UI);
		}
		else	{
			scheduledFuture = scheduler.scheduleAtFixedRate(this, 0, mInterval, TimeUnit.SECONDS);
		}
		
		return true;
	}
	
	private boolean stopListening()	{
		if(mInterval == 0)	{
			mSensorManager.unregisterListener(this);
		}
		else	{
			scheduledFuture.cancel(false);
		}
		
		return false;
	}
	
	private int mProvisionalState	= UNREGISTERED;
	
	private void registerState(int newState)	{
		if(newState == mProvisionalState && newState != mCurrentState)	{
			changeProfile(mSettings.getInt("Profile"+newState, PROFILE_NORMAL));
			mCurrentState = newState;
		}
		else	{
			mProvisionalState = newState;
		}
	}
	
	private boolean about(float a, float b)	{
		return Math.abs(a - b) < mSensitivity;
	}
	
	/* Once the whole rigmarole is done and we have finally decided we want to change some
	 * settings, here's where it happens.
	 */	
	private void changeProfile(int newProfile)	{
		AudioManager manager = (AudioManager) getSystemService(AUDIO_SERVICE);
		
		switch(newProfile)	{
			case PROFILE_NORMAL:
				notifyChange(R.string.profile_loud);
				manager.setRingerMode(RINGER_MODE_NORMAL);
				break;
			case PROFILE_VIBRATE:
				notifyChange(R.string.profile_vibrate);
				manager.setRingerMode(RINGER_MODE_VIBRATE);
				break;
			case PROFILE_SILENT:
				notifyChange(R.string.profile_silent);
				manager.setRingerMode(RINGER_MODE_SILENT);
				break;
			/*
			case PROFILE_VIBRING:
				notifyChange(R.string.profile_vibring);
				manager.setRingerMode(RINGER_MODE_NORMAL);
				manager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, 
						VIBRATE_SETTING_ON);
				manager.setVibrateSetting(VIBRATE_TYPE_RINGER, 
						VIBRATE_SETTING_ON);
				break;
			case PROFILE_NIGHT:
				notifyChange(R.string.profile_night);
				manager.setRingerMode(RINGER_MODE_NORMAL);
				break;*/
			default:
				Log.e("OTH", "Attempted to change to an unknown profile, ID:" + newProfile);
		}
	}
	
	private void notifyChange(int change)	{
		if(mNotify)	{
			Toast.makeText(this, getText(change), Toast.LENGTH_SHORT).show();
		}
	}
}
